공유메모리 세그먼트 — 크기·key·권한·수명과 삭제

NOTE

공유메모리는 프로세스의 heap이 아니라 OS 커널이 따로 만들어 관리하는 메모리 영역이다. 그 한 덩어리를 세그먼트(Segment) 라 부른다. 이 노트는 세그먼트의 크기는 누가 정하는지, 어떻게 찾고(key), 누가 접근할 수 있는지(권한), 언제 사라지는지(수명·삭제) 를 정리한다.

상위 허브: 공유메모리 한눈에 보기


1. 세그먼트란, 그리고 heap과 무엇이 다른가

세그먼트 = 커널이 “하나의 단위”로 관리하는 공유메모리 한 덩어리. 한 서버에 공유메모리가 여러 개 있을 수 있어서, 각각을 구분해 부르는 단위가 필요해 이렇게 부른다. ipcs -m 목록의 한 줄이 세그먼트 하나다.

커널의 공유메모리 관리 표
┌───────┬──────────┬──────┬───────┬──────┬────────┬─────────────┐
│ shmid │ key      │ 크기 │ 소유자 │ 권한 │ 붙은 수 │ 물리 메모리  │
├───────┼──────────┼──────┼───────┼──────┼────────┼─────────────┤
│ 101   │ 0x1234   │ 40KB │ tap   │ 0660 │ 3      │ → RAM 위치  │
│ 102   │ 0x5678   │ 8KB  │ root  │ 0600 │ 1      │ → RAM 위치  │
└───────┴──────────┴──────┴───────┴──────┴────────┴─────────────┘
   ↑ 각 줄이 세그먼트 하나 (프로세스가 아니라 커널이 가진 정보)
프로세스 A 가상 주소 공간          프로세스 B 가상 주소 공간
┌──────────────┐                  ┌──────────────┐
│ stack        │                  │ stack        │
│ 공유메모리 ───┼──┐            ┌──┼─── 공유메모리 │  ← mmap 영역에 매핑
│ heap(malloc) │  │            │  │ heap(malloc) │  ← 프로세스 전용
│ data/text    │  │            │  │ data/text    │
└──────────────┘  ▼            ▼  └──────────────┘
              ┌────────────────────┐
              │ 물리 메모리 (RAM)   │  ← 같은 물리 페이지 하나
              │ 세그먼트            │
              └────────────────────┘
heap (malloc)공유메모리
소유그 프로세스 하나커널이 관리하는 독립 객체 (프로세스가 죽어도 남을 수 있음)
다른 프로세스 접근불가가능 (각자 자기 주소 공간에 매핑)
주소프로세스마다 고유같은 세그먼트를 프로세스마다 다른 가상 주소에 붙일 수 있음
위치heap 영역mmap 영역 (가상 주소 공간)

2. 크기는 누가 정하나

만드는 프로세스가 요청하고, 커널이 한도 안에서 허용한다.

/* System V 방식 */
int shmid = shmget(key, 4096 * 10, IPC_CREAT | 0660);
/*                      ^^^^^^^^^^   ^^^^^^^^^^^^^^^^
                        크기(바이트)   생성 플래그 | 접근 권한 */
 
/* POSIX 방식 */
int fd = shm_open("/myshm", O_CREAT | O_RDWR, 0660);
ftruncate(fd, 4096 * 10);          /* 크기는 ftruncate로 지정 */
  • 내부적으로 페이지(4KB) 단위로 할당된다.
  • 한 번 정하면 못 바꾼다. 더 필요하면 새로 만들어야 하므로 최대치를 미리 계산해서 잡는다.
  • 이미 있는 세그먼트에 붙는 쪽은 크기를 정하지 않는다. 같은 key로 shmget하면 기존 것을 얻고, 요청 크기가 실제보다 크면 EINVAL이 난다. (붙기만 할 때는 크기 인자로 0을 넘기는 것이 안전)

OS 한도 (리눅스)

커널 파라미터의미
kernel.shmmax세그먼트 하나의 최대 크기
kernel.shmall시스템 전체 공유메모리 총량 (페이지 단위)
kernel.shmmni세그먼트 최대 개수

최신 커널은 기본값이 매우 크게 잡혀 있어 잘 걸리지 않지만, 구형 환경·컨테이너·보안 하드닝된 서버에서는 한도에 걸려 shmget이 실패하는 경우가 있다. POSIX 방식(shm_open)은 /dev/shm 파일시스템의 용량이 한도가 된다.

ipcs -m -l              # 현재 한도 확인
sysctl kernel.shmmax    # 개별 확인
ipcs -m                 # 지금 존재하는 세그먼트 목록 (key, shmid, 소유자, 권한, 크기, nattch)
df -h /dev/shm          # POSIX 방식의 용량

3. 세그먼트를 찾는 방법 — key 와 shmid

서로 모르는 두 프로세스가 같은 세그먼트를 쓰려면 약속된 식별자가 필요하다.

방식식별자비고
System V정수 key (ftok(경로, 프로젝트ID) 또는 고정값)관련: ftok과 IPC 키 생성, 이름-키 매핑 테이블
POSIX이름 문자열 "/myshm"/dev/shm/myshm에 대응
번호역할유일성
key”이 이름으로 찾아라”는 검색용 이름표살아 있는(삭제 예약 안 된) 세그먼트 중에서만 유일
shmid커널이 부여하는 실제 식별자세그먼트마다 항상 다름

shmget 플래그에 따른 동작 (key가 이미 존재할 때)

호출동작
shmget(key, size, IPC_CREAT | 0660)이미 있으면 기존 것을 그대로 반환(새로 안 만듦)
shmget(key, size, IPC_CREAT | IPC_EXCL | 0660)이미 있으면 EEXIST 에러 (“내가 새로 만드는 게 맞는지” 확인)
shmget(key, 0, 0)없으면 실패. 사용만 하는 쪽이 쓰는 형태

실무에서는 생성 담당(기동 데몬) 은 IPC_CREAT | IPC_EXCL(고아 세그먼트 감지), 사용 담당은 IPC_CREAT 없이 붙기만 하는 식으로 역할을 나눈다.


4. 접근 권한 — 누가 붙을 수 있나

같은 서버에는 여러 계정(root, 서비스 계정, 다른 개발자)의 프로세스가 돌고, 공유메모리에는 민감한 설정값이나 상태가 들어 있을 수 있다. 그래서 접근 통제가 필요하다. 방식은 파일 권한(chmod)과 같다.

0660 = 소유자(rw-)  그룹(rw-)  기타(---)
        읽기·쓰기    읽기·쓰기   접근 불가

커널이 세그먼트마다 소유자 uid/gid와 mode를 같이 저장해 두고, shmget/shm_open/shmat 때 호출 프로세스의 uid/gid와 대조한다. 안 맞으면 EACCES.

실무에서 자주 겪는 일

프로세스를 root로 한 번 띄웠다가 일반 계정으로 다시 띄우면, 이전에 root가 만든 세그먼트에 권한이 없어 기동이 실패한다. (ipcs -m의 소유자 열 확인)

붙일 때도 프로세스별로 권한을 고를 수 있다

void *p = shmat(shmid, NULL, 0);           /* 읽기/쓰기로 붙임 */
void *r = shmat(shmid, NULL, SHM_RDONLY);  /* 읽기 전용으로 붙임 (쓰면 SIGSEGV) */

POSIX는 mmap의 prot(PROT_READ/PROT_WRITE)로 같은 효과를 낸다. → mmap 노트


5. “메모리인데 파일을 만든다고?”

두 방식이 다르다.

System V (shmget)POSIX (shm_open)
파일이 생기나아니요. 커널 내부 표에 항목만 생김/dev/shm/이름에 파일처럼 보이는 것이 생김
ls로 보이나안 보임 (ipcs -m으로만 확인)보임 (ls -l /dev/shm)
데이터는 어디에RAMRAM (/dev/shm은 tmpfs, 디스크 아님, 재부팅 시 사라짐)
파일 권한 모델rwx 모양을 빌려 쓴 별도 체계실제 파일 권한 그대로

POSIX가 파일처럼 만들어진 이유는 유닉스의 “모든 것을 파일로 다룬다” 철학이다. 파일 인터페이스를 재활용하면 이름으로 찾기, 권한, fd, mmap, ls/rm을 새로 만들 필요가 없다. 파일은 “이름 + 권한 + 핸들”을 제공하는 껍데기이고 실제 데이터는 RAM에 있다.

ftok의 파일도 저장소가 아니다

ftok("/some/file", 'A')에 넣는 경로는 그 파일의 inode 번호와 문자 하나를 섞어 key 숫자를 만드는 재료일 뿐, 파일 내용은 쓰지 않는다. 서로 다른 프로세스가 같은 key를 계산하도록 “공통으로 아는 경로”를 쓰는 것이다.


6. 수명과 해제 — free 가 아니다

생성(shmget/shm_open) → 붙임(shmat/mmap) → 사용 → 떼어냄(shmdt/munmap) → 삭제(IPC_RMID/shm_unlink)
                         └─ 프로세스별 ──────────────┘   └─ 세그먼트 단위 ──┘
붙인 방법떼어내기 (내 연결만 해제)세그먼트 삭제
mmapmunmap(p, len)POSIX면 shm_unlink("/name")
shmatshmdt(p)shmctl(shmid, IPC_RMID, NULL) / 셸: ipcrm -m <shmid>

free()를 부르면 안 된다(malloc이 만든 메모리가 아니므로 크래시).

두 단계는 역할이 다르다.

  • 떼어내기: 내 프로세스의 연결만 끊는다. 공유메모리와 다른 프로세스의 연결은 그대로.
  • 삭제: 세그먼트 자체를 없애겠다는 표시. 마지막으로 붙은 프로세스가 떼어낼 때 실제로 사라진다.

안 하면?

  • 떼어내기를 깜빡: 프로세스 종료 시 커널이 자동 정리. 단 장시간 도는 데몬이 계속 붙이기만 하면 주소 공간이 누적된다.
  • 삭제를 깜빡: 세그먼트가 커널에 남는다. 비정상 종료가 반복되면 ipcs -m에 고아 세그먼트가 쌓여, 다음 기동 때 “이미 존재(EEXIST)”·“크기 불일치(EINVAL)“로 실패하거나 RAM을 차지한다. 정리는 ipcs -m(nattch 0 확인) → ipcrm -m <shmid>.

7. 삭제는 누가 할 수 있나

“만든 프로세스”가 아니라 “uid가 맞는 프로세스” 가 한다. 기준은 읽기/쓰기 권한 비트가 아니라 소유자/생성자 uid 일치다.

호출한 프로세스삭제 가능 여부
세그먼트 소유자(owner) uid와 같은 계정가능
생성자(creator) uid와 같은 계정가능
root(특권 보유)가능
그 외 계정EPERM

같은 계정의 아무 프로세스(또는 ipcrm -m)가 지울 수 있다. 접근만 하던 프로세스도 같은 uid면 IPC_RMID를 호출할 수 있다는 뜻이다.

쓰는 중에 삭제하면 — “삭제 예약”

1. A가 세그먼트 생성, A·B·C가 붙어서 사용 중        key=0x1234  shmid=101  nattch=3
2. B가 IPC_RMID 호출
     ├─ 세그먼트에 "삭제 예정(dest)" 표시만 붙음
     ├─ 이미 붙은 A·B·C는 계속 정상 사용 가능
     ├─ 옛 세그먼트의 key는 IPC_PRIVATE(0)로 바뀜  key=0x00000000  shmid=101  status=dest
     │     → 새로 shmget(0x1234)해도 옛 세그먼트를 못 찾는다
     └─ 마지막 프로세스가 shmdt/종료하는 순간 실제로 소멸

파일의 unlink(지워도 열려 있는 동안은 유지)와 같은 동작이다. (리눅스 기준이며 다른 유닉스는 세부가 다를 수 있다)

“key가 중복되는 것 아닌가?” — 아니다

삭제 예약된 옛 세그먼트는 key를 반납(0x00000000)하므로 같은 key가 두 개 존재하는 일은 없다. 옛것(shmid 101)과 새것(shmid 102)은 shmid가 달라 별개의 메모리다.

진짜 문제: 데이터가 갈라진다

기존에 붙어 있던 프로세스 ──→ shmid 101 (옛 세그먼트, dest 상태)
재기동한 프로세스         ──→ shmid 102 (같은 key로 새로 만든 세그먼트)

코드로는 둘 다 "key 0x1234"를 쓰지만 실제로는 서로 다른 메모리를 본다.
에러가 안 나서 원인 찾기가 매우 어렵다.

설계 원칙: 삭제 책임은 한 곳에

“생성자가 삭제 책임자”는 커널이 강제하는 규칙이 아니라 설계상의 약속이다. 보통 기동/종료를 담당하는 데몬(관리 프로세스)만 생성과 삭제를 하고, 나머지 프로세스는 shmat/shmdt(또는 mmap/munmap)만 하도록 코드와 운영 절차로 정한다. 고아 정리 스크립트도 ipcs -m의 nattch가 0인지 확인한 뒤 지운다. (malloc/free의 소유권 개념과 비교: 동적 할당 소유권)


8. 한눈에 정리

질문답
누가 제공하나OS 커널 (heap과 별개의 커널 관리 객체)
크기는생성하는 프로세스가 지정, 페이지 단위, 이후 변경 불가. 상한 shmmax/shmall/shmmni
찾는 방법System V key(ftok 등) / POSIX 이름 문자열. 커널 내부 식별자는 shmid
접근 제어소유자·그룹·기타 퍼미션(0660). 붙일 때 읽기전용 선택 가능
파일을 만드나System V 아니요 / POSIX /dev/shm(tmpfs, 데이터는 RAM)
해제free 아님 → shmdt/munmap (떼기) + IPC_RMID/shm_unlink (삭제)
삭제 권한소유자·생성자 uid 일치(또는 root). 접근만 하던 프로세스도 가능
쓰는 중 삭제삭제 예약(dest), key 반납, 마지막 프로세스가 떼면 소멸
재기동 시 위험옛 세그먼트와 새 세그먼트로 데이터가 갈라짐 → 삭제 책임을 한 곳에

관련 문서